
// ---- End Project Data ----


// ---- File IRTimeSync.t ----

// Before Script for "vIRTimeSync"
//	===========================================================================================
//	IRTimeSync
//
//	Copyright © 1994 Apple Computer, Inc.
//	All rights reserved.
//
//	Modification Status
//	YY/MM/DD	Name				Comments
//	94/05/20	Jim Schram		Initial Development during World Wide Developers Conference
//	94/05/25	Jim Schram		Released as |IRTimeSync:PIEDTS| version 2 sample code
//	-------------------------------------------------------------------------------------------------------------
//	NOTE:	Time synchronization is accurate +/- one second and currently does not account for beam time.
//	===========================================================================================

constant kDebugShowPhases := nil;													// true to show phase codes in vTitle area
constant kIdleFrequency := 135;														// number of milliseconds between idles
constant kTimeoutTicks := 900;														// number of ticks to wait before timing out a connection

constant kPhase_Idle := 0;																// phase codes for the vTimeMachine state machine
constant kPhase_CountDown := 1;
constant kPhase_Send := 2;	
constant kPhase_Connecting := 3;
constant kPhase_Receive := 5;
constant kPhase_Listening := 6;
constant kPhase_Abort := 7;
constant kPhase_AbortQuiet := 8;
constant kPhase_Disconnect := 9;
constant kPhase_Disconnecting := 10;
constant kPhase_Disconnected := 11;

//	-------------------------------------------------------------------------------------------------------------

constant kTitle := "Newton Time Synchronization Via IR Beam";			// title of the application

constant kMessage_Send := "Looking for receiver...";						// progress/status messages
constant kMessage_Receive := "Looking for sender...";
constant kMessage_Stopping := "Stopping...";
constant kMessage_Timeout := "No response.";
constant kMessage_TimeHasBeenSet := "Time has been set.";
constant kMessage_PortInUse := "IR port in use?";
constant kMessage_GenericError := "An error occured!";
DefConst('kMessage_BadTime, "Bad time value received." & unicodeCR & "Please try again.");

DefConst('kMessage_CountDownFunc,												// progress/status message constructor functions
	func(pendingSeconds)
	return "Time will be set in"
				&& NumberStr(pendingSeconds)
				&&	if pendingSeconds = 1 then "second."
						else "seconds." & unicodeCR & "Please wait...");

DefConst('kMessage_IdleFunc,
	func()
	begin
		local t := TimeInSeconds() mod 60;
		return DateNTime(Time()) & " and " & NumberStr(t) & if t = 1 then " second" else " seconds";
	end);

constant kButton_Send := "Send Time";												// button names
constant kButton_Receive := "Receive Time";
constant kButton_Stop := "Stop";

//	-------------------------------------------------------------------------------------------------------------

DefConst('kSysBeepSyncFunc,															// replaces GetRoot():SysBeep() with a similar SYNCHRONOUS version
	func()
	if userConfiguration.beepSound <> 'none and userConfiguration.soundVolume > 0 then
			PlaySoundSync(ROM_soundoff.(userConfiguration.beepSound)));

//	-------------------------------------------------------------------------------------------------------------

OpenResFile(HOME & "IRTimeSync.rsrc");											// load our attention sound (played when the time has been set)
DefConst('kAlarmSound, GetSound11("IRTimeSyncAlarmSound"));		// keep the size of this sample app down by using an 11Khz sample rate
CloseResFile();

//	===========================================================================================


vIRTimeSync :=
   {
    viewSetupDoneScript:
      func()
      begin
      	GetRoot().extrasDrawer:Close();
      	:MSetPhase(nil);
      	:SetUpIdle(135);
      end,
    viewQuitScript:
      func()
      begin
      	if fEndPoint then
      		fEndPoint:MDisconnect();
      end,
    viewFlags: 576,
    viewIdleScript:
      func()
      begin
      	if kDebugShowPhases then begin
      			local xT := NumberStr(TimeInSeconds() mod 60);
      			local xPh := if fPhase then NumberStr(fPhase) else "NIL";
      			local xCon := if fEndPoint and fEndPoint.fIsConnected then "TRUE" else "FALSE";
      			local xSt := if fEndPoint then NumberStr(fEndPoint:State()) else "NIL";
      			SetValue(vTitle, 'text, "T: " & xT & "   Ph: " & xPh & "   Con: " & xCon & "   St: " & xSt );
      			RefreshViews();
      		end;
      		
      	if not fPhase or fPhase = kPhase_Idle then begin
      			:MSetMessage(call kMessage_IdleFunc with ());
      		end
      	
      	else if fPhase = kPhase_CountDown then begin
      			local elapsedTime := Abs(timeinseconds() - fStartTime);
      			local secondsToWait := 60 - fSeconds;
      			if elapsedTime >= secondsToWait then begin
      					SetTime(fMinutes + 1);
      					:MSetMessage(kMessage_TimeHasBeenSet);
      					PlaySound(kAlarmSound);
      					fMinutes := nil;
      					fSeconds := nil;
      					:MSetPhase(kPhase_Disconnect);
      				end
      			else
      				:MSetMessage(call kMessage_CountDownFunc with (secondsToWait - elapsedTime));
      			end
      	
      	else if fPhase = kPhase_Send then begin
      			fEndPoint :=	{	_proto:		fEndPointConfig,
         									_parent:	self	};
      			local err := fEndPoint:Instantiate(fEndPoint, nil);
      			if err then begin
      					:MSetError(kMessage_PortInUse, err);
      					:MSetPhase(nil);
      				end
      			else begin
      				vReceiveTime:Hide();
      				SetValue(vSendTime, 'text, kButton_Stop);
      				:MSetMessage(kMessage_Send);
      				fEndPoint:MConnect();
      				fTimeoutTicks := Ticks();
      				:MSetPhase(kPhase_Connecting);
      			end;
      		end
      	
      	else if fPhase = kPhase_Connecting then begin
      			if Abs(Ticks() - fTimeoutTicks) > kTimeoutTicks then begin
      					:MSetMessage(kMessage_Timeout);
      					call kSysBeepSyncFunc with ();
      					call kSysBeepSyncFunc with ();
      					:MSetPhase(kPhase_AbortQuiet);
      				end
      			else if fEndPoint.fIsConnected then begin
      					fEndPoint:MSendTime();
      					:MSetPhase(kPhase_Disconnect);
      				end;
      		end
      	
      	else if fPhase = kPhase_Disconnect then begin
      			if fEndPoint then begin
      					fEndPoint:MDisconnect();
      					:MSetPhase(kPhase_Disconnecting);
      				end
      			else
      				:MSetPhase(kPhase_Disconnected);
      		end
      	
      	else if fPhase = kPhase_Disconnecting then begin
      		end
      	
      	else if fPhase = kPhase_Disconnected then begin
      			SetValue(vReceiveTime, 'text, kButton_Receive);
      			SetValue(vSendTime, 'text, kButton_Send);
      			vReceiveTime:Show();
      			vSendTime:Show();
      			:MSetPhase(nil);
      		end
      	
      	else if fPhase = kPhase_Abort then begin
      			:MSetMessage(kMessage_Stopping);
      			:MSetPhase(kPhase_AbortQuiet);
      		end
      	
      	else if fPhase = kPhase_AbortQuiet then begin
      			:MSetPhase(kPhase_Disconnect);
      		end
      	
      	else if fPhase = kPhase_Receive then begin
      			fEndPoint :=	{	_proto:		fEndPointConfig,
         									_parent:	self	};
      			local err := fEndPoint:Instantiate(fEndPoint, nil);
      			if err then begin
      					:MSetError(kMessage_PortInUse, err);
      					:MSetPhase(nil);
      				end
      			else begin
      				vSendTime:Hide();
      				SetValue(vReceiveTime, 'text, kButton_Stop);
      				:MSetMessage(kMessage_Receive);
      				fEndPoint:MListen();
      				fTimeoutTicks := Ticks();
      				:MSetPhase(kPhase_Listening);
      			end;
      		end
      	
      	else if fPhase = kPhase_Listening then begin
      			if Abs(Ticks() - fTimeoutTicks) > kTimeoutTicks then begin
      					:MSetMessage(kMessage_Timeout);
      					call kSysBeepSyncFunc with ();
      					call kSysBeepSyncFunc with ();
      					:MSetPhase(kPhase_AbortQuiet);
      				end
      			else if fEndPoint.fIsConnected and fMinutes and fSeconds then begin
      					if fMinutes > 0 and fSeconds >=0 and fSeconds <= 59 then
      						:MSetPhase(kPhase_CountDown)
      					else begin
      							:MSetMessage(kMessage_BadTime);
      							call kSysBeepSyncFunc with ();
      							call kSysBeepSyncFunc with ();
      							:MSetPhase(kPhase_AbortQuiet);
      						end;
      				end;
      		end;
      	
      	return kIdleFrequency;
      end,
    fPhase: nil		// holds the next phase to be executed for the idle machine,
    fEndPointConfig:
      {
      	_proto:						protoEndpoint,
      	
      	fIsConnected:			nil,
      	
      	receiveFlags:			kFrame,
      	
      	configOptions:			[
      										{	label:		kCMSSlowIR,
      											type:		'service,
      											opCode:		opSetRequired,	},
      									],
      									
      	exceptionHandler:		func(exception)			// deferred actions exception context is LAME -- the only action we dare take is to say we've handled the exception
      									begin
      										if HasSlot(exception, 'data)
      											and exception.data <> nil
      											and exception.data <> -16005						// cancelled (not really an error)
      											and exception.data <> -16013 then begin		// cancelled (not really an error)
      												:MSetError(kMessage_GenericError, exception.data);
      												call kSysBeepSyncFunc with ();
      												call kSysBeepSyncFunc with ();
      												call kSysBeepSyncFunc with ();
      												if fPhase then
      													if fPhase = kPhase_Connecting
      													or	fPhase = kPhase_Listening
      													or	fPhase = kPhase_CountDown then
      														:MSetPhase(kPhase_Abort);
      											end;
      										
      										return true;
      									end,
      									
      	FReceiveTime:			{	inputForm:			'string,				// data is a string of this format:  IRTimeSyncVersion1X123456Y123456Z
      										endCharacter:		$Z,
      										inputScript:			func(ep, data)
      																	begin
      																		ep._parent.fStartTime := TimeInSeconds();				// note the time we received the beam
      																		ep:SetInputSpec(nil);													// don't want to be retriggered
      																		
      																		ep._parent.fMinutes := 0;
      																		ep._parent.fSeconds := 0;
      																		
      																		local p1 := StrPos(data, "X", 0);								// suck out the version info
      																		if p1 then begin
      																				if StrEqual(SubStr(data, 0, p1), "IRTimeSyncVersion1") then begin
      																						local p2 := StrPos(data, "Y", p1);				// suck out the time in minutes (since midnight January 1, 1904)
      																						if p2 then begin
      																								local p3 := StrPos(data, "Z", p2);		// suck out the time in seconds (0-59)
      																								if p3 then begin
      																										ep._parent.fMinutes := RintToL(StringToNumber(SubStr(data, p1 + 1, p2 - p1 - 1)));
      																										ep._parent.fSeconds := RintToL(StringToNumber(SubStr(data, p2 + 1, p3 - p2 - 1)));
      																									end;
      																								end;
      																					end
      																				else 
      																					ep._parent:MSetError(kMessage_BadTime, -666);
      																			end;
      																	end,	},
      	
      	MSendTime:				func()
      									begin
      										local s :=	"IRTimeSyncVersion1X"										// IR Time Sync Version ID
      														& NumberStr(Time()) & "Y"									// minutes since midnight January 1, 1904
      														& NumberStr(TimeInSeconds() mod 60) & "Z";		// seconds past the minute (0-59)
      										:Output(s, kFrame);
      										:FlushOutput();
      									end,
      	
      	MListen:					func()
      									begin
      										fMinutes := nil;
      										fSeconds := nil;
      										AddDeferredAction(	func(ep)
      																		begin
      																			ep.fIsConnected := ep:Listen(nil) = nil;
      																			if ep.fIsConnected then
      																				ep:SetInputSpec(ep.FReceiveTime);
      																		end,
      																	[self]);
      									end,
      	
      	MConnect:					func()
      									begin
      										AddDeferredAction(	func(ep)
      																		begin
      																			ep.fIsConnected := ep:Connect(nil, nil) = nil;
      																		end,
      																	[self]);
      									end,
      	
      	MDisconnect:			func()
      									begin
      										self.nextInputSpec := nil;
      										:SetInputSpec(nil);
      										if fIsConnected and :State() = kDataXfer then
      											:Release()
      										else
      											:Abort();
      										AddDelayedAction(func(ep)
      																		begin
      																			if ep.fIsConnected then
      																				ep:Disconnect();
      																			ep:Dispose();
      																			ep._parent.fEndPoint := nil;
      																			if ep._parent.fPhase then
      																				ep._parent:MSetPhase(kPhase_Disconnected);
      																		end,
      																	[self], 666);
      									end,
      },
    viewBounds: {left: 0, top: 0, right: 228, bottom: 110},
    _proto: protoFloater,
    MSetPhase:
      func(phase)
      begin
      	fPhase := phase;
      end,
    fStartTime: 0,
    viewJustify: 80,
    fMinutes:
      nil		// nil until we receive ANY value, 0 = illegal value, other = number of minutes since midnight January 1, 1904
    ,
    fEndPoint: nil,
    declareSelf: 'base,
    viewClickScript:
      func(unit)
      begin
      	:Drag(unit, nil);
      end,
    debug: "vIRTimeSync",
    fTimeoutTicks: 0,
    MSetError:
      func(message, error)
      begin
      	:MSetMessage(message & unicodeCR & NumberStr(error));
      	call kSysBeepSyncFunc with ();
      end,
    fSeconds:
      nil		// nil until we receive ANY value, 0 to 59 (inclusive) are legal values, other = illegal values
    ,
    MSetMessage:
      func(message)
      begin
      	SetValue(vMessage, 'text, Clone(message));
      	RefreshViews();
      end
   };

vTitle := /* child of vIRTimeSync */
   {text: "",
    viewBounds: {left: 0, top: 4, right: 0, bottom: 20},
    viewJustify: 8388658,
    _proto: protoStaticText,
    debug: "vTitle"
   };
// View vTitle is declared to vIRTimeSync

// After Script for "vTitle"
thisView := vTitle;
thisView.text := kTitle;



vMessage := /* child of vIRTimeSync */
   {text: "",
    viewBounds: {left: 16, top: 24, right: 208, bottom: 55},
    viewJustify: 2,
    viewFont: simpleFont9,
    _proto: protoStaticText,
    debug: "vMessage"
   };
// View vMessage is declared to vIRTimeSync



vReceiveTime := /* child of vIRTimeSync */
   {text: "",
    buttonClickScript:
      func()
      begin
      	if not fPhase then
      		:MSetPhase(kPhase_Receive)
      	
      	else if fPhase = kPhase_Listening or fPhase = kPhase_CountDown then
      		:MSetPhase(kPhase_Abort);
      end,
    viewBounds: {left: 18, top: 57, right: 102, bottom: 77},
    _proto: protoTextButton,
    debug: "vReceiveTime"
   };
// View vReceiveTime is declared to vIRTimeSync

// After Script for "vReceiveTime"
thisView := vReceiveTime;
thisView.text := kButton_Receive;



vSendTime := /* child of vIRTimeSync */
   {text: "",
    buttonClickScript:
      func()
      begin
      	if not fPhase then
      		:MSetPhase(kPhase_Send)
      	
      	else if fPhase = kPhase_Connecting then
      		:MSetPhase(kPhase_Abort);
      end,
    viewBounds: {left: 122, top: 57, right: 206, bottom: 77},
    _proto: protoTextButton,
    debug: "vSendTime"
   };
// View vSendTime is declared to vIRTimeSync

// After Script for "vSendTime"
thisView := vSendTime;
thisView.text := kButton_Send;



vStatusBar := /* child of vIRTimeSync */
   {_proto: protoStatus, debug: "vStatusBar"};







// ---- Beginning of section for non used Layout files ----

// End of output